"""
Sing-box 代理辅助工具
通过配置文件动态管理sing-box服务中的代理配置
"""
import subprocess
import json
import os
import tempfile
import time
import socket
import signal
import atexit
import hashlib
from typing import Dict, Optional, Tuple
from pathlib import Path
from api.utils.logger import get_logger

logger = get_logger("singbox_proxy_helper")
 
# 全局变量存储运行中的配置信息
# 结构: {config_hash: {'port': int, 'tag': str, 'ref_count': int, 'uses_config_file': bool}}
_running_singbox_processes = {}


class SingBoxProxyManager:
    """Sing-box代理管理器 - 通过本地SOCKS5端口桥接"""
    
    @staticmethod
    def _generate_config_hash(v2ray_config: Dict) -> str:
        """
        生成配置的唯一哈希值（用于判断配置是否相同）
        
        Args:
            v2ray_config: V2Ray配置字典
            
        Returns:
            配置的MD5哈希值
        """
        # 提取关键配置参数用于生成哈希
        key_params = {
            'type': v2ray_config.get('type'),
            'host': v2ray_config.get('host'),
            'port': v2ray_config.get('port'),
            'uuid': v2ray_config.get('uuid'),
            'password': v2ray_config.get('password'),
            'security': v2ray_config.get('security'),
            'network': v2ray_config.get('network'),
            'sni': v2ray_config.get('sni'),
            'pbk': v2ray_config.get('pbk'),
            'sid': v2ray_config.get('sid'),
            'fp': v2ray_config.get('fp'),
            'spx': v2ray_config.get('spx'),
            'flow': v2ray_config.get('flow'),
            'encryption': v2ray_config.get('encryption'),
        }
        
        # 生成JSON字符串并计算MD5
        config_str = json.dumps(key_params, sort_keys=True)
        return hashlib.md5(config_str.encode()).hexdigest()
    
    @staticmethod
    def find_free_port(start_port: int = 10808, exclude_ports: set = None) -> int:
        """
        找到一个可用的端口（仅基于配置文件，保证连续性）
        
        Args:
            start_port: 起始端口号
            exclude_ports: 要排除的端口集合（已被配置文件使用的）
            
        Returns:
            可用的端口号
        """
        if exclude_ports is None:
            exclude_ports = set()
        
        # 简化逻辑：只检查exclude_ports，不做实际socket绑定
        # 这样可以保证端口连续性（10808、10809、10810...）
        for port in range(start_port, start_port + 1000):
            if port not in exclude_ports:
                return port
        
        raise RuntimeError(f"无法在 {start_port}-{start_port+1000} 范围内找到可用端口")
    
    @staticmethod
    def generate_singbox_config(v2ray_config: Dict, socks_port: int) -> Dict:
        """
        根据v2ray配置生成Sing-box配置文件
        
        Args:
            v2ray_config: 解析后的v2ray配置
            socks_port: 本地SOCKS5监听端口
            
        Returns:
            Sing-box配置字典
        """
        proxy_type = v2ray_config.get('type', 'vmess')
        logger.info(f"生成Sing-box配置: 协议类型={proxy_type}")
        
        # 基础配置
        config = {
            "log": {
                "level": "error"
            },
            "inbounds": [
                {
                    "type": "socks",
                    "tag": "socks-in",
                    "listen": "127.0.0.1",
                    "listen_port": socks_port,
                    "users": []
                }
            ],
            "outbounds": [],
            "route": {
                "rules": []
            }
        }
        
        # 根据不同协议生成outbound配置
        if proxy_type == 'vmess':
            outbound = SingBoxProxyManager._generate_vmess_outbound(v2ray_config)
        elif proxy_type == 'vless':
            outbound = SingBoxProxyManager._generate_vless_outbound(v2ray_config)
        elif proxy_type == 'trojan':
            outbound = SingBoxProxyManager._generate_trojan_outbound(v2ray_config)
        elif proxy_type == 'shadowsocks':
            outbound = SingBoxProxyManager._generate_shadowsocks_outbound(v2ray_config)
        else:
            raise ValueError(f"不支持的代理类型: {proxy_type}")
        
        config['outbounds'].append(outbound)
        
        return config
    
    @staticmethod
    def _generate_vmess_outbound(config: Dict) -> Dict:
        """生成VMess outbound配置（Sing-box格式）"""
        outbound = {
            "type": "vmess",
            "tag": "proxy-out",
            "server": config['host'],
            "server_port": config['port'],
            "uuid": config['uuid'],
            "security": config.get('security', 'auto'),
            "alter_id": config.get('alter_id', 0)
        }
        
        # 添加TLS配置
        if config.get('tls'):
            # 如果未明确指定allowInsecure,则默认为True(忽略证书验证)
            allow_insecure = config.get('allowInsecure', '1') == '1'
            
            outbound['tls'] = {
                "enabled": True,
                "insecure": allow_insecure
            }
            if config.get('sni'):
                outbound['tls']['server_name'] = config['sni']
            
            logger.info(f"配置VMess TLS: insecure={allow_insecure}")
        
        # 添加传输层配置
        network = config.get('network', 'tcp')
        if network == 'ws':
            outbound['transport'] = {
                "type": "ws",
                "path": config.get('path', '/'),
                "headers": {}
            }
            if config.get('host_header'):
                outbound['transport']['headers']['Host'] = config['host_header']
        
        return outbound
    
    @staticmethod
    def _generate_vless_outbound(config: Dict) -> Dict:
        """生成VLESS outbound配置（Sing-box格式）"""
        outbound = {
            "type": "vless",
            "tag": "proxy-out",
            "server": config['host'],
            "server_port": config['port'],
            "uuid": config['uuid']
        }
        
        # 添加flow（如果存在且非空）
        flow = config.get('flow', '')
        if flow:
            outbound["flow"] = flow
        
        # 添加TLS/Reality配置
        security = config.get('security', 'none')
        
        if security == 'reality':
            # Reality协议配置
            # 如果未明确指定allowInsecure,则默认为True(忽略证书验证)
            allow_insecure = config.get('allowInsecure', '1') == '1'
            
            outbound['tls'] = {
                "enabled": True,
                "server_name": config.get('sni', config['host']),
                "insecure": allow_insecure,
                "reality": {
                    "enabled": True,
                    "public_key": config.get('pbk', ''),
                    "short_id": config.get('sid', '')
                },
                "utls": {
                    "enabled": True,
                    "fingerprint": config.get('fp', 'chrome')
                }
            }
            
            logger.info(f"配置Reality: sni={outbound['tls']['server_name']}, fp={outbound['tls']['utls']['fingerprint']}, insecure={allow_insecure}")
            
        elif security in ['tls', 'xtls']:
            # 如果未明确指定allowInsecure,则默认为True(忽略证书验证)
            allow_insecure = config.get('allowInsecure', '1') == '1'
            
            outbound['tls'] = {
                "enabled": True,
                "insecure": allow_insecure
            }
            if config.get('sni'):
                outbound['tls']['server_name'] = config['sni']
            
            logger.info(f"配置TLS: insecure={allow_insecure}")
        
        # 添加传输层配置
        network = config.get('network', 'tcp')
        if network == 'ws':
            outbound['transport'] = {
                "type": "ws",
                "path": config.get('path', '/'),
                "headers": {}
            }
            if config.get('host_header'):
                outbound['transport']['headers']['Host'] = config['host_header']
        
        return outbound
    
    @staticmethod
    def _generate_trojan_outbound(config: Dict) -> Dict:
        """生成Trojan outbound配置（Sing-box格式）"""
        outbound = {
            "type": "trojan",
            "tag": "proxy-out",
            "server": config['host'],
            "server_port": config['port'],
            "password": config['password']
        }
        
        # Trojan默认使用TLS
        # 如果未明确指定allowInsecure,则默认为True(忽略证书验证)
        allow_insecure = config.get('allowInsecure', '1') == '1'
        
        outbound['tls'] = {
            "enabled": True,
            "insecure": allow_insecure
        }
        
        if config.get('sni'):
            outbound['tls']['server_name'] = config['sni']
        
        logger.info(f"配置Trojan TLS: insecure={allow_insecure}")
        
        return outbound
    
    @staticmethod
    def _generate_shadowsocks_outbound(config: Dict) -> Dict:
        """生成Shadowsocks outbound配置（Sing-box格式）"""
        outbound = {
            "type": "shadowsocks",
            "tag": "proxy-out",
            "server": config['host'],
            "server_port": config['port'],
            "method": config.get('method', 'aes-256-gcm'),
            "password": config['password']
        }
        
        return outbound
    
    @staticmethod
    def convert_to_socks5(v2ray_config_json: str) -> Tuple[bool, Optional[Dict], str]:
        """将v2ray配置转换为本地SOCKS5代理配置"""
        try:
            from api.service.singbox_process_manager import SingBoxProcessManager
            v2ray_config = json.loads(v2ray_config_json)
            
            config_hash = SingBoxProxyManager._generate_config_hash(v2ray_config)
            tag = f"proxy_{config_hash[:8]}"

            # 先获取已存在代理的端口（如果有）
            existing_port = None
            if tag in SingBoxProcessManager._proxy_configs:
                existing_port = SingBoxProcessManager._proxy_configs[tag]['port']
            
            # 如果代理不存在，分配新端口
            if existing_port is None:
                used_ports = set(info['port'] for info in SingBoxProcessManager._proxy_configs.values())
                port = SingBoxProxyManager.find_free_port(exclude_ports=used_ports)
            else:
                port = existing_port
            
            # 直接调用ProcessManager（会自动处理引用计数）
            success, message = SingBoxProcessManager.add_proxy(tag, port, v2ray_config)
            
            if not success:
                return False, None, message

            # 返回实际使用的端口（可能是复用的）
            actual_port = SingBoxProcessManager._proxy_configs[tag]['port']
            socks5_config = {
                'type': 'socks5',
                'host': '127.0.0.1',
                'port': actual_port,
                'v2ray_config': v2ray_config,
                'config_hash': config_hash,
                'tag': tag
            }
            return True, socks5_config, message
        except Exception as e:
            logger.error(f"转换失败: {e}", exc_info=True)
            return False, None, f"转换失败: {str(e)}"
    
    @staticmethod
    def _find_singbox_executable() -> Optional[str]:
        """查找sing-box可执行文件路径"""
        # 优先使用本地安装的sing-box
        from api.service.singbox_manager import SingBoxManager
        
        local_singbox = SingBoxManager.get_singbox_path()
        if local_singbox:
            logger.info(f"使用本地Sing-box: {local_singbox}")
            return local_singbox
        
        # 回退到系统PATH中的sing-box
        possible_paths = [
            '/usr/local/bin/sing-box',
            '/usr/bin/sing-box',
            'sing-box',  # 系统PATH中
        ]
        
        for path in possible_paths:
            try:
                result = subprocess.run(
                    [path, 'version'],
                    capture_output=True,
                    timeout=5
                )
                if result.returncode == 0:
                    logger.info(f"找到系统Sing-box: {path}")
                    return path
            except (FileNotFoundError, subprocess.TimeoutExpired):
                continue
        
        return None
    
    @staticmethod
    def remove_proxy_by_tag(tag: str) -> Tuple[bool, str]:
        """通过tag移除代理"""
        try:
            from api.service.singbox_process_manager import SingBoxProcessManager
            return SingBoxProcessManager.remove_proxy(tag)
        except Exception as e:
            logger.error(f"移除代理失败: {e}", exc_info=True)
            return False, f"移除失败: {str(e)}"
    
    @staticmethod
    def cleanup_old_configs():
        """清理所有旧的配置文件（应用启动时调用）"""
        config_dir = Path("runtime/singbox/configs")
        if not config_dir.exists():
            return
        
        logger.info("清理旧的Sing-box配置文件...")
        cleaned_count = 0
        
        for config_file in config_dir.glob("config_*.json"):
            try:
                config_file.unlink()
                cleaned_count += 1
            except Exception as e:
                pass
        
        if cleaned_count > 0:
            logger.info(f"已清理 {cleaned_count} 个旧配置文件")


def cleanup_singbox_processes():
    """清理所有Sing-box进程（程序退出时调用）"""
    logger.info("清理所有Sing-box配置...")
    for config_hash in list(_running_singbox_processes.keys()):
        SingBoxProxyManager.remove_proxy_config(config_hash)


# 注册退出时清理函数
atexit.register(cleanup_singbox_processes)


def get_singbox_proxy_config(v2ray_config_json: str) -> Dict:
    """
    获取代理配置（用于连接）
    
    通过配置文件添加到sing-box服务，返回本地SOCKS5桥接配置
    
    Args:
        v2ray_config_json: v2ray配置JSON字符串
        
    Returns:
        代理配置字典
    """
    try:
        from api.service.singbox_process_manager import SingBoxProcessManager
        import json
        
        v2ray_config = json.loads(v2ray_config_json)
        config_hash = SingBoxProxyManager._generate_config_hash(v2ray_config)
        tag = f"proxy_{config_hash[:8]}"
        
        # 先获取已存在代理的端口（如果有）
        existing_port = None
        if tag in SingBoxProcessManager._proxy_configs:
            existing_port = SingBoxProcessManager._proxy_configs[tag]['port']
        
        # 如果代理不存在，分配新端口
        if existing_port is None:
            used_ports = set(info['port'] for info in SingBoxProcessManager._proxy_configs.values())
            port = SingBoxProxyManager.find_free_port(exclude_ports=used_ports)
        else:
            port = existing_port
        
        # 调用ProcessManager添加配置（会自动处理引用计数）
        success, message = SingBoxProcessManager.add_proxy(tag, port, v2ray_config)
        
        if success:
            # 返回实际使用的端口（可能是复用的）
            actual_port = SingBoxProcessManager._proxy_configs[tag]['port']
            return {
                'type': 'socks5',
                'host': '127.0.0.1',
                'port': actual_port,
                'config_hash': config_hash,
                'tag': tag
            }
        else:
            logger.error(f"[获取代理配置] 失败: {message}")
            return {
                'type': 'unsupported',
                'error': message
            }
            
    except Exception as e:
        logger.error(f"[获取代理配置] 异常: {e}", exc_info=True)
        return {
            'type': 'unsupported',
            'error': f"配置失败: {str(e)}"
        }


# 向后兼容的别名
get_v2ray_proxy_config = get_singbox_proxy_config